package com.zeny.express.framework.log.aop;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;

import com.zeny.express.framework.log.support.ExpressionRootObject;
import com.zeny.express.framework.log.support.LazyParamAwareEvaluationContext;

public class BusinessExpressionEvaluator {

	public static final Object NO_RESULT = new Object();
	
	public static final String RETURN_KEY = "result";
	
	private static int INIT_SIZE = 128;

	private final SpelExpressionParser parser = new SpelExpressionParser();

	// shared param discoverer since it caches data internally
	private final ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

	private final Map<String, Expression> idCache = new ConcurrentHashMap<String, Expression>(INIT_SIZE);

	private final Map<String, Expression> codeCache = new ConcurrentHashMap<String, Expression>(INIT_SIZE);

	private final Map<String, Expression> nameCache = new ConcurrentHashMap<String, Expression>(INIT_SIZE);

	private final Map<String, Method> targetMethodCache = new ConcurrentHashMap<String, Method>(INIT_SIZE);

	public EvaluationContext createEvaluationContext(Method method,
			Object[] args, Object target, Class<?> targetClass) {
		return createEvaluationContext(method, args, target, targetClass,
				NO_RESULT);
	}

	public EvaluationContext createEvaluationContext(Method method,
			Object[] args, Object target, Class<?> targetClass,
			final Object result) {
		ExpressionRootObject rootObject = new ExpressionRootObject(
				method, args, target, targetClass);
		LazyParamAwareEvaluationContext evaluationContext = new LazyParamAwareEvaluationContext(
				rootObject, this.paramNameDiscoverer, method, args,
				targetClass, this.targetMethodCache);
		if (result != NO_RESULT) {
			evaluationContext.setVariable(RETURN_KEY, result);
		}
		return evaluationContext;
	}

	public Object id(String expression, Method method, EvaluationContext evalContext) {
		return getExpression(this.idCache, expression, method).getValue(evalContext);
	}

	public Object code(String expression, Method method, EvaluationContext evalContext) {
		return getExpression(this.codeCache, expression, method).getValue(evalContext);
	}

	public Object name(String expression, Method method, EvaluationContext evalContext) {
		return getExpression(this.nameCache, expression, method).getValue(evalContext);
	}

	private Expression getExpression(Map<String, Expression> cache,
			String expression, Method method) {
		String key = toString(method, expression);
		Expression rtn = cache.get(key);
		if (rtn == null) {
			rtn = this.parser.parseExpression(expression);
			cache.put(key, rtn);
		}
		return rtn;
	}

	private String toString(Method method, String expression) {
		StringBuilder sb = new StringBuilder();
		sb.append(method.getDeclaringClass().getName());
		sb.append("#");
		sb.append(method.toString());
		sb.append("#");
		sb.append(expression);
		return sb.toString();
	}
	
}
